#import "BWDocumentUndo.h"

#import "BWStitchView.h"
#import "BWGrid.h"
#import "BWEvenweave.h"
#import "BWStitch.h"
#import "BWStitchArray.h"

#define NAME_SENDER_MASK (NSNotificationCoalescingOnName)


@implementation BWBorkStitchDocument (StitchUndoMethods)

- (void) setupStitchUndo
{
    NSNotificationCenter *center;
    center = [NSNotificationCenter defaultCenter];

    [center addObserver: self
            selector: @selector(stitchWidthChangedNotification:)
            name: @"stitchWidthChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchBendFactorChangedNotification:)
            name: @"stitchBendFactorChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchBendDisplacementChangedNotification:)
            name: @"stitchBendDisplacementChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchBendFlatnessChangedNotification:)
            name: @"stitchBendFlatnessChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchFillColorChangedNotification:)
            name: @"stitchFillColorChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchArrowLengthChangedNotification:)
            name: @"stitchArrowLengthChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchArrowWeightChangedNotification:)
            name: @"stitchArrowWeightChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchArrowColorChangedNotification:)
            name: @"stitchArrowColorChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchStartLabelDisplacementChangedNotification:)
            name: @"stitchStartLabelDisplacementChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchEndLabelDisplacementChangedNotification:)
            name: @"stitchEndLabelDisplacementChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchStartLabelDisplacementAngleChangedNotification:)
            name: @"stitchStartLabelDisplacementAngleChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchEndLabelDisplacementAngleChangedNotification:)
            name: @"stitchEndLabelDisplacementAngleChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchLocationChangedNotification:)
            name: @"stitchLocationChanged"
            object: self];

    [center addObserver: self
            selector: @selector(stitchBendChangedNotification:)
            name: @"stitchBendChanged"
            object: self];

} // setupStitchUndo


- (void) orderStitch: (BWStitch *) stitch
          toPosition: (int) newPosition
          actionName: (NSString *) actionName
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    int position;
    position = [stitches indexOfStitch: stitch];

    [[undoManager prepareWithInvocationTarget: self]
        orderStitch: stitch
        toPosition: position
        actionName: actionName];

    [stitch retain];

    [stitches removeStitch: stitch];
    [stitches insertStitch: stitch  atIndex: newPosition];

    [stitch release];

    if (![undoManager isUndoing]) {
        [undoManager setActionName: actionName];
    }

    [self setNeedsDisplay: YES];
    
    [self updateToolUI];

} // orderStitch



- (NSNotification *) notificationWithName: (NSString *) notificationName
                                   stitch: (BWStitch *) stitch
{
    NSDictionary *userInfo;
    userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                                 stitch, @"stitch", nil];
    NSNotification *notification;
    notification = 
        [NSNotification notificationWithName: notificationName
                        object: self
                        userInfo: userInfo];

    return (notification);
    
} // notificationWithName


- (NSNotification *) notificationWithName: (NSString *) notificationName
                                  indexes: (NSIndexSet *) indexes
                                    value: (id) value
{
    if (value == nil) {
        value == [NSNull null];
    }

    // can't put the value into the notification user info(e) because
    // the notification queue returns the first notification enqueued,
    // which is a stale value

    [notificationValue release];
    notificationValue = [value retain];

    NSDictionary *userInfo;
    userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                                 indexes, @"indexes", nil];

    NSNotification *notification;
    notification = 
        [NSNotification notificationWithName: notificationName
                        object: self
                        userInfo: userInfo];

    return (notification);
    
} // notificationWithName indexes


- (id) notificationValue
{
    return (notificationValue);

} // notificationValue


- (void) flipStitchIndexes: (NSIndexSet *) indexes;
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        flipStitchIndexes: indexes];

    NSArray *stitchesToFlip = [stitches stitchesAtIndexes: indexes];

    NSEnumerator *enumerator;
    enumerator = [stitchesToFlip objectEnumerator];
    
    BWStitch *stitch;
    while (stitch = [enumerator nextObject]) {
        [stitch flip];
    }

    if (![undoManager isUndoing]) {
        [undoManager setActionName: @"Flip Stitch"];
    }

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // flipStitchIndexes



- (void) addStitches: (NSArray *) newStitches
           atIndexes: (NSIndexSet *) indexes
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        removeStitchesAtIndexes: indexes];

    [stitches addStitches: newStitches
              atIndexes: indexes];

    if (![undoManager isUndoing]) {
        [undoManager setActionName: @"Add Stitches"];
    }

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // addStitches


- (void) removeStitchesAtIndexes: (NSIndexSet *) stitchIndexes
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    NSArray *nukeStitches;
    nukeStitches = [stitches stitchesAtIndexes: stitchIndexes];

    [[undoManager prepareWithInvocationTarget: self]
        addStitches: nukeStitches  atIndexes: stitchIndexes];

    [stitches removeStitchesAtIndexes: stitchIndexes];

    if (![undoManager isUndoing]) {
        [undoManager setActionName: @"Remove Stitches"];
    }

    [self setNeedsDisplay: YES];

    [self updateToolUI];
    
} // removeStitchesAtIndexes


- (void) removeStitch: (BWStitch *) stitch
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    int position;
    position = [stitches indexOfStitch: stitch];

    [[undoManager prepareWithInvocationTarget: self]
        addStitch: stitch  atPosition: position];

    [stitches removeStitch: stitch];

    if (![undoManager isUndoing]) {
        [undoManager setActionName: @"Remove Stitch"];
    }

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // removeStitch


- (void) addStitch: (BWStitch *) stitch
        atPosition: (int) position
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        removeStitch: stitch];

    [stitches insertStitch: stitch  atIndex: position];

    if (![undoManager isUndoing]) {
        [undoManager setActionName: @"Add Stitch"];
    }
    
    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // addStitch


- (void) returnStitches: (NSArray *) originals
              toIndexes: (NSIndexSet *) indexes
     bringForwardOnUndo: (BOOL) bringForward
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    int count = [originals count];

    if (bringForward) {
        [[undoManager prepareWithInvocationTarget: self]
            bringIndexesForward: indexes];
        [stitches removeLastStitches: count];

    } else {
        [[undoManager prepareWithInvocationTarget: self]
            sendIndexesBack: indexes];
        [stitches removeFirstStitches: count];
    }

    [stitches insertStitches: originals
              atIndexes: indexes];

} // returnStitches


- (void) bringIndexesForward: (NSIndexSet *) indexes
{
    NSArray *originals;
    originals = [stitches stitchesAtIndexes: indexes];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        returnStitches: originals
        toIndexes: indexes
        bringForwardOnUndo: YES];

    [stitches removeStitchesAtIndexes: indexes];
    [stitches addStitchesFromArray: originals];

    if (![undoManager isUndoing]) {
        [undoManager setActionName: @"Move to Front"];
    }
    
    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // bringIndexesForward


- (void) sendIndexesBack: (NSIndexSet *) indexes
{
    NSArray *originals;
    originals = [stitches stitchesAtIndexes: indexes];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        returnStitches: originals
        toIndexes: indexes
        bringForwardOnUndo: NO];

    [stitches removeStitchesAtIndexes: indexes];
    [stitches addStitchesFromArrayToBeginning: originals];

    if (![undoManager isUndoing]) {
        [undoManager setActionName: @"Send to Back"];
    }
    
    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // sendIndexesBack


- (void) shiftStitchesHorizontal: (int) hDirection
                        vertical: (int) vDirection
                         indexes: (NSIndexSet *) indexes
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        shiftStitchesHorizontal: -hDirection
        vertical: -vDirection
        indexes: indexes];

    if (indexes == nil) {
        NSRange range = NSMakeRange (0, [stitches count]);
        indexes = [NSIndexSet indexSetWithIndexesInRange: range];
    }

    // ? should pre-flight to prevent running off top or left?

    NSArray *stitchesToShift = [stitches stitchesAtIndexes: indexes];

    NSEnumerator *enumerator;
    enumerator = [stitchesToShift objectEnumerator];

    BWStitch *stitch;
    while (stitch = [enumerator nextObject]) {
        BWGrid_Square startSquare, endSquare;
        BWGrid_SquarePart startPart, endPart;

        [stitch getStartSquare: &startSquare  part: &startPart];
        [stitch getEndSquare: &endSquare  part: &endPart];

        startSquare.x += hDirection;
        startSquare.y += vDirection;

        endSquare.x += hDirection;
        endSquare.y += vDirection;

        [stitch setStartSquare: startSquare  part: startPart];
        [stitch setEndSquare: endSquare  part: endPart];
    }

    [undoManager setActionName: @"Shift Stitches"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // shiftStitchesHorizontal


- (void) setArrow: (BWStitch_ArrowKind) arrow
        forStitch: (BWStitch *) stitch
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setArrow: [stitch arrow]  forStitch: stitch];

    [stitch setArrow: arrow];

    if (![undoManager isUndoing]) {
        if (arrow == BWStitch_NoArrow) {
            [undoManager setActionName: @"Remove Arrow"];
        } else {
            [undoManager setActionName: @"Add Arrow"];
        }
    }

    [self setNeedsDisplay: YES];
    [self updateToolUI];

}  // setArrow



- (void) setArrow: (BWStitch_ArrowKind) arrow
       forIndexes: (NSIndexSet *) indexes
{
    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        NSArray *victims = [stitches stitchesAtIndexes: indexes];
        NSEnumerator *enumerator = [victims objectEnumerator];

        BWStitch *stitch;
        while (stitch = [enumerator nextObject]) {
            [self setArrow: arrow
                  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

    [self setNeedsDisplay: YES];
    [self updateToolUI];

} // setArrow forIndexes


// --------------------------------------------------

- (void) setStartLabelVisibility: (BOOL) startOnOff
              endLabelVisibility: (BOOL) endOnOff
                       forStitch: (BWStitch *) stitch;
{
    BOOL oldStart, oldEnd;
    oldStart = [stitch startLabelVisibility];
    oldEnd = [stitch endLabelVisibility];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setStartLabelVisibility: oldStart
        endLabelVisibility: oldEnd
        forStitch: stitch];

    [stitch setStartLabelVisibility: startOnOff];
    [stitch setEndLabelVisibility: endOnOff];

    [undoManager setActionName: @"Change Label Visibility"];


    [self setNeedsDisplay: YES];
    [self updateToolUI];

}  // setStartLabelVisiblity


- (void) setStartLabelVisibility: (BOOL) startOnOff
              endLabelVisibility: (BOOL) endOnOff
                      forIndexes: (NSIndexSet *) indexes
{
    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        NSArray *victims = [stitches stitchesAtIndexes: indexes];
        NSEnumerator *enumerator = [victims objectEnumerator];

        BWStitch *stitch;
        while (stitch = [enumerator nextObject]) {
            [self setStartLabelVisibility: startOnOff
                  endLabelVisibility: endOnOff
                  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

    [self setNeedsDisplay: YES];
    [self updateToolUI];

} // setSTartLabelVisibility forIndexes



- (void) setCustomStartLabel: (NSString *) label
                   forStitch: (BWStitch *) stitch
{
    NSString *oldStartLabel;
    oldStartLabel = [stitch customStartLabel];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setCustomStartLabel: oldStartLabel
        forStitch: stitch];

    [stitch setCustomStartLabel: label];

    // need to check both because these are included in a grouping,
    // and don't want the action name to clobber the name on a redo
    if (![undoManager isUndoing] && ![undoManager isRedoing]) {
        [undoManager setActionName: @"Change Start Label"];
    }

    [self setNeedsDisplay: YES];
    [self updateToolUI];

} // setStartLabel



- (void) setCustomStartLabel: (NSString *) label
                  forIndexes: (NSIndexSet *) indexes
{
    NSUndoManager *undoManager = [self undoManager];
    
    [undoManager beginUndoGrouping]; {
        
        NSArray *victims = [stitches stitchesAtIndexes: indexes];
        NSEnumerator *enumerator = [victims objectEnumerator];
        
        BWStitch *stitch;
        while (stitch = [enumerator nextObject]) {
            [self setCustomStartLabel: label
                  forStitch: stitch];
        }
        
    } [undoManager endUndoGrouping];
    
    [self setNeedsDisplay: YES];
    [self updateToolUI];
    
} // setCustomStartLabel forIndexes


- (void) setCustomEndLabel: (NSString *) label
                 forStitch: (BWStitch *) stitch
{
    NSString *oldEndLabel;
    oldEndLabel = [stitch customEndLabel];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setCustomEndLabel: oldEndLabel
        forStitch: stitch];

    [stitch setCustomEndLabel: label];

    // need to check both because these are included in a grouping,
    // and don't want the action name to clobber the name on a redo
    if (![undoManager isUndoing] && ![undoManager isRedoing]) {
        [undoManager setActionName: @"Change End Label"];
    }

    [self setNeedsDisplay: YES];
    [self updateToolUI];

} // setCustomEndLabel


- (void) setCustomEndLabel: (NSString *) label
                forIndexes: (NSIndexSet *) indexes
{
    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        NSArray *victims = [stitches stitchesAtIndexes: indexes];
        NSEnumerator *enumerator = [victims objectEnumerator];

        BWStitch *stitch;
        while (stitch = [enumerator nextObject]) {
            [self setCustomEndLabel: label
                  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

    [self setNeedsDisplay: YES];
    [self updateToolUI];

} // setCustomEndLabel forIndexes


- (void) removeCustomLabelsForStitch: (BWStitch *) stitch
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        [self setCustomStartLabel: nil
              forStitch: stitch];
        [self setCustomEndLabel: nil
              forStitch: stitch];
        
        [undoManager setActionName: @"Remove Custom Labels"];
    } [undoManager endUndoGrouping];

    [self setNeedsDisplay: YES];
    [self updateToolUI];

} // removeLabelsForStitch


- (void) removeCustomLabelsForIndexes: (NSIndexSet *) indexes
{
    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        NSArray *victims = [stitches stitchesAtIndexes: indexes];
        NSEnumerator *enumerator = [victims objectEnumerator];

        BWStitch *stitch;
        while (stitch = [enumerator nextObject]) {
            [self removeCustomLabelsForStitch: stitch];
        }

    } [undoManager endUndoGrouping];

    [self setNeedsDisplay: YES];
    [self updateToolUI];

} // removeCustomLabelsForIndexes


- (void) setStartLabelInside: (BOOL) inside
                  forIndexes: (NSIndexSet *) indexes
{
    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        NSArray *victims = [stitches stitchesAtIndexes: indexes];
        NSEnumerator *enumerator = [victims objectEnumerator];

        BWStitch *stitch;
        while (stitch = [enumerator nextObject]) {
            [self setStartLabelInside: inside
                  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

    [self setNeedsDisplay: YES];
    [self updateToolUI];

} // setStartLabelInside forIndexes


- (void) setStartLabelInside: (BOOL) inside
                   forStitch: (BWStitch *) stitch
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setStartLabelInside: [stitch startLabelInsideStitch]
        forStitch: stitch];

    [stitch setStartLabelInsideStitch: inside];

    if (![undoManager isUndoing]) {
        if (inside) {
            [undoManager setActionName: @"Start Label Inside Stitch"];
        } else {
            [undoManager setActionName: @"Start Label Outside Stitch"];
        }
    }

    [self setNeedsDisplay: YES];
    [self updateToolUI];
    
} // setStartLabelInside forStitch


- (void) setEndLabelInside: (BOOL) inside
                forIndexes: (NSIndexSet *) indexes
{
    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        NSArray *victims = [stitches stitchesAtIndexes: indexes];
        NSEnumerator *enumerator = [victims objectEnumerator];

        BWStitch *stitch;
        while (stitch = [enumerator nextObject]) {
            [self setEndLabelInside: inside
                  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

    [self setNeedsDisplay: YES];
    [self updateToolUI];

} // setEndLabelInside forIndexes


- (void) setEndLabelInside: (BOOL) inside
                 forStitch: (BWStitch *) stitch
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setEndLabelInside: [stitch endLabelInsideStitch]
        forStitch: stitch];

    [stitch setEndLabelInsideStitch: inside];

    if (![undoManager isUndoing]) {
        if (inside) {
            [undoManager setActionName: @"End Label Inside Stitch"];
        } else {
            [undoManager setActionName: @"End Label Outside Stitch"];
        }
    }

    [self setNeedsDisplay: YES];
    [self updateToolUI];
    
} // setEndLabelInside


- (void) setLabelFont: (NSFont *) font
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setLabelFont: labelFont];

    [font retain];
    [labelFont release];
    labelFont = font;

    [stitchView setLabelFont: font];

    [undoManager setActionName: @"Change Label Font"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setLabelFont



// --------------------------------------------------

- (void) setWidth: (float) newWidth  forStitch: (BWStitch *) stitch
{
    float oldWidth;
    oldWidth = [stitch width];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setWidth: oldWidth  forStitch: stitch];

    [stitch setWidth: newWidth];

    [undoManager setActionName: @"Change Stitch Width"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setWidth:forStitch:


- (void) stitchWidthChangedNotification: (NSNotification *) notification
{
    NSIndexSet *indexes;
    indexes = [[notification userInfo] objectForKey: @"indexes"];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        int i;
        for (i = 0; i < [victims count]; i++) {
            BWStitch *stitch = [victims objectAtIndex: i];

            float newWidth = [stitch transientWidth];
            [stitch setTransientWidth: BWStitch_NO_TRANSIENT_VALUE];

            [self setWidth: newWidth  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

} // stitchWidthChangedNotification


- (void) changeWidth: (float) width
          forIndexes: (NSIndexSet *) indexes
{
    NSNumber *number = [NSNumber numberWithFloat: width];

    NSNotification *notification;
    notification = [self notificationWithName: @"stitchWidthChanged"
                         indexes: indexes
                         value: number];

    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientWidth: width];
    }

    [self setNeedsDisplay: YES];

} // changeWidth:forStitch:


// --------------------------------------------------

- (void) setBendFactor: (float) newBendFactor  forStitch: (BWStitch *) stitch
{
    float oldBendFactor;
    oldBendFactor = [stitch bendFactor];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setBendFactor: oldBendFactor  forStitch: stitch];

    [stitch setBendFactor: newBendFactor];

    [undoManager setActionName: @"Bend Stitch"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setBendFactor:forStitch:


- (void) stitchBendFactorChangedNotification: (NSNotification *) notification
{
    NSIndexSet *indexes;
    indexes = [[notification userInfo] objectForKey: @"indexes"];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        int i;
        for (i = 0; i < [victims count]; i++) {
            BWStitch *stitch = [victims objectAtIndex: i];

            float newBendFactor = [stitch transientBendFactor];
            [stitch setTransientBendFactor: BWStitch_NO_TRANSIENT_VALUE];
            
            [self setBendFactor: newBendFactor  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

} // stitchBendFactorChangedNotification


- (void) changeBendFactor: (float) bendFactor
               forIndexes: (NSIndexSet *) indexes
{
    NSNumber *number = [NSNumber numberWithFloat: bendFactor];

    NSNotification *notification;
    notification = [self notificationWithName:  @"stitchBendFactorChanged"
                         indexes: indexes
                         value: number];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientBendFactor: bendFactor];
    }

    [self setNeedsDisplay: YES];

} // changeBendFactor:forStitch:



// --------------------------------------------------

- (void) setBendDisplacement: (float) newBendDisplacement  
                   forStitch: (BWStitch *) stitch
{
    float oldBendDisplacement;
    oldBendDisplacement = [stitch bendDisplacement];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setBendDisplacement: oldBendDisplacement  forStitch: stitch];

    [stitch setBendDisplacement: newBendDisplacement];

    [undoManager setActionName: @"Bend Displacement"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setBendDisplacement:forStitch:


- (void) stitchBendDisplacementChangedNotification: (NSNotification *) notification
{
    NSIndexSet *indexes;
    indexes = [[notification userInfo] objectForKey: @"indexes"];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        int i;
        for (i = 0; i < [victims count]; i++) {
            BWStitch *stitch = [victims objectAtIndex: i];

            float newBendDisplacement = [stitch transientBendDisplacement];
            [stitch setTransientBendDisplacement: BWStitch_NO_TRANSIENT_VALUE];
            
            [self setBendDisplacement: newBendDisplacement  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

} // stitchBendDisplacementChangedNotification


- (void) changeBendDisplacement: (float) bendDisplacement
                     forIndexes: (NSIndexSet *) indexes
{
    NSNumber *number = [NSNumber numberWithFloat: bendDisplacement];

    NSNotification *notification;
    notification = [self notificationWithName: @"stitchBendDisplacementChanged"
                         indexes: indexes
                         value: number];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientBendDisplacement: bendDisplacement];
    }

    [self setNeedsDisplay: YES];

} // changeBendDisplacement:forStitch:



// --------------------------------------------------

- (void) setBendFlatness: (float) newBendFlatness  
               forStitch: (BWStitch *) stitch
{
    float oldBendFlatness;
    oldBendFlatness = [stitch bendFlatness];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setBendFlatness: oldBendFlatness  forStitch: stitch];

    [stitch setBendFlatness: newBendFlatness];

    [undoManager setActionName: @"Bend Flatness"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setBendFlatness:forStitch:


- (void) stitchBendFlatnessChangedNotification: (NSNotification *) notification
{
    NSIndexSet *indexes;
    indexes = [[notification userInfo] objectForKey: @"indexes"];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        int i;
        for (i = 0; i < [victims count]; i++) {
            BWStitch *stitch = [victims objectAtIndex: i];

            float newBendFlatness = [stitch transientBendFlatness];
            [stitch setTransientBendFlatness: BWStitch_NO_TRANSIENT_VALUE];
            
            [self setBendFlatness: newBendFlatness  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

} // stitchBendFlatnessChangedNotification


- (void) changeBendFlatness: (float) bendFlatness
                 forIndexes: (NSIndexSet *) indexes
{
    NSNumber *number = [NSNumber numberWithFloat: bendFlatness];

    NSNotification *notification;
    notification = [self notificationWithName: @"stitchBendFlatnessChanged"
                         indexes: indexes
                         value: number];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientBendFlatness: bendFlatness];
    }

    [self setNeedsDisplay: YES];

} // changeBendFlatness:forStitch:



// --------------------------------------------------

- (void) setFillColor: (NSColor *) newFillColor  
            forStitch: (BWStitch *) stitch
{
    NSColor *oldFillColor;
    oldFillColor = [stitch fillColor];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setFillColor: oldFillColor  forStitch: stitch];

    [stitch setFillColor: newFillColor];

    [undoManager setActionName: @"Change Stitch Color"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setFillColor:forStitch:


- (void) stitchFillColorChangedNotification: (NSNotification *) notification
{
    NSIndexSet *indexes;
    indexes = [[notification userInfo] objectForKey: @"indexes"];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        int i;
        for (i = 0; i < [victims count]; i++) {
            BWStitch *stitch = [victims objectAtIndex: i];

            NSColor * newFillColor = [stitch transientFillColor];
            [stitch setTransientFillColor: NULL];
            [self setFillColor: newFillColor  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

} // stitchFillColorChangedNotification


- (void) changeFillColor: (NSColor *) fillColor
              forIndexes: (NSIndexSet *) indexes
{
    NSNotification *notification;
    notification = [self notificationWithName: @"stitchFillColorChanged"
                         indexes: indexes
                         value: fillColor];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientFillColor: fillColor];
    }

    [self setNeedsDisplay: YES];

} // changeFillColor:forStitch:



// --------------------------------------------------

- (void) setArrowLength: (float) newArrowLength  forStitch: (BWStitch *) stitch
{
    float oldArrowLength;
    oldArrowLength = [stitch arrowLength];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setArrowLength: oldArrowLength  forStitch: stitch];

    [stitch setArrowLength: newArrowLength];

    [undoManager setActionName: @"Change Arrow Length"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setArrowLength:forStitch:


- (void) stitchArrowLengthChangedNotification: (NSNotification *) notification
{
    NSIndexSet *indexes;
    indexes = [[notification userInfo] objectForKey: @"indexes"];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        int i;
        for (i = 0; i < [victims count]; i++) {
            BWStitch *stitch = [victims objectAtIndex: i];

            float newArrowLength = [stitch transientArrowLength];
            [stitch setTransientArrowLength: BWStitch_NO_TRANSIENT_VALUE];
            
            [self setArrowLength: newArrowLength  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

} // stitchArrowLengthChangedNotification


- (void) changeArrowLength: (float) arrowLength
                forIndexes: (NSIndexSet *) indexes
{
    NSNumber *number = [NSNumber numberWithFloat: arrowLength];

    NSNotification *notification;
    notification = [self notificationWithName: @"stitchArrowLengthChanged"
                         indexes: indexes
                         value: number];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientArrowLength: arrowLength];
    }

    [self setNeedsDisplay: YES];

} // changeArrowLength:forStitch:



// --------------------------------------------------

- (void) setArrowWeight: (float) newArrowWeight  forStitch: (BWStitch *) stitch
{
    float oldArrowWeight;
    oldArrowWeight = [stitch arrowWeight];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setArrowWeight: oldArrowWeight  forStitch: stitch];

    [stitch setArrowWeight: newArrowWeight];

    [undoManager setActionName: @"Change Arrow Weight"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setArrowWeight:forStitch:


- (void) stitchArrowWeightChangedNotification: (NSNotification *) notification
{
    NSIndexSet *indexes;
    indexes = [[notification userInfo] objectForKey: @"indexes"];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        int i;
        for (i = 0; i < [victims count]; i++) {
            BWStitch *stitch = [victims objectAtIndex: i];

            float newArrowWeight = [stitch transientArrowWeight];
            [stitch setTransientArrowWeight: BWStitch_NO_TRANSIENT_VALUE];
            
            [self setArrowWeight: newArrowWeight  forStitch: stitch];
        }
        
    } [undoManager endUndoGrouping];

} // stitchArrowWeightChangedNotification


- (void) changeArrowWeight: (float) arrowWeight
                forIndexes: (NSIndexSet *) indexes
{
    NSNumber *number = [NSNumber numberWithFloat: arrowWeight];

    NSNotification *notification;
    notification = [self notificationWithName: @"stitchArrowWeightChanged"
                         indexes: indexes
                         value: number];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientArrowWeight: arrowWeight];
    }

    [self setNeedsDisplay: YES];

} // changeArrowWeight:forStitch:


// --------------------------------------------------

- (void) setArrowColor: (NSColor *) newArrowColor  
             forStitch: (BWStitch *) stitch
{
    NSColor *oldArrowColor;
    oldArrowColor = [stitch arrowColor];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setArrowColor: oldArrowColor  forStitch: stitch];

    [stitch setArrowColor: newArrowColor];

    [undoManager setActionName: @"Change Arrow Color"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setArrowColor:forStitch:


- (void) stitchArrowColorChangedNotification: (NSNotification *) notification
{
    NSIndexSet *indexes;
    indexes = [[notification userInfo] objectForKey: @"indexes"];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        int i;
        for (i = 0; i < [victims count]; i++) {
            BWStitch *stitch = [victims objectAtIndex: i];

            NSColor * newArrowColor = [stitch transientArrowColor];
            [stitch setTransientArrowColor: NULL];
            
            [self setArrowColor: newArrowColor  forStitch: stitch];
        }

    } [undoManager endUndoGrouping];

} // stitchArrowColorChangedNotification


- (void) changeArrowColor: (NSColor *) fillColor
               forIndexes: (NSIndexSet *) indexes
{
    NSNotification *notification;
    notification = [self notificationWithName: @"stitchArrowColorChanged"
                         indexes: indexes
                         value: fillColor];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientArrowColor: fillColor];
    }

    [self setNeedsDisplay: YES];

} // changeArrowColor:forStitch:



// --------------------------------------------------

- (void) setStartLabelDisplacement: (float) newStartLabelDisplacement  
                         forStitch: (BWStitch *) stitch
{
    float oldStartLabelDisplacement;
    oldStartLabelDisplacement = [stitch startLabelDisplacement];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setStartLabelDisplacement: oldStartLabelDisplacement  
        forStitch: stitch];

    [stitch setStartLabelDisplacement: newStartLabelDisplacement];

    [undoManager setActionName: @"Change Stitch Start Label Displacement"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setStartLabelDisplacement:forStitch:


- (void) setStartLabelDisplacement: (float) newStartLabelDisplacement
                        forIndexes: (NSIndexSet *) indexes
{
    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        
        [stitch setTransientStartLabelDisplacement: 
                    BWStitch_NO_TRANSIENT_VALUE];
        
        [self setStartLabelDisplacement: newStartLabelDisplacement  
              forStitch: stitch];
    }

} // startStartLabelDisplacement 


- (void) stitchStartLabelDisplacementChangedNotification: 
    (NSNotification *) notification
{
    NSDictionary *userinfo = [notification userInfo];

    NSIndexSet *indexes;
    indexes = [userinfo objectForKey: @"indexes"];

    NSNumber *number = [self notificationValue];
    float newStartLabelDisplacement = [number floatValue];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {

        [self setStartLabelDisplacement: newStartLabelDisplacement
              forIndexes: indexes];
        
    } [undoManager endUndoGrouping];

} // stitchStartLabelDisplacementChangedNotification


- (void) changeStartLabelDisplacement: (float) startLabelDisplacement
                           forIndexes: (NSIndexSet *) indexes
{
    NSNumber *number = [NSNumber numberWithFloat: 
                                     startLabelDisplacement];
    NSNotification *notification;
    notification = [self notificationWithName:
                             @"stitchStartLabelDisplacementChanged"
                         indexes: indexes
                         value: number];

    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientStartLabelDisplacement: startLabelDisplacement];
    }

    [self setNeedsDisplay: YES];

} // changeStartLabelDisplacement:forIndexes:


// --------------------------------------------------


- (void) setEndLabelDisplacement: (float) newEndLabelDisplacement  
                       forStitch: (BWStitch *) stitch
{
    float oldEndLabelDisplacement;
    oldEndLabelDisplacement = [stitch endLabelDisplacement];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setEndLabelDisplacement: oldEndLabelDisplacement  
        forStitch: stitch];

    [stitch setEndLabelDisplacement: newEndLabelDisplacement];

    [undoManager setActionName: @"Change Stitch End Label Displacement"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setEndLabelDisplacement:forStitch:


- (void) setEndLabelDisplacement: (float) newEndLabelDisplacement
                      forIndexes: (NSIndexSet *) indexes
{
    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        
        [stitch setTransientEndLabelDisplacement: 
                    BWStitch_NO_TRANSIENT_VALUE];
        
        [self setEndLabelDisplacement: newEndLabelDisplacement  
              forStitch: stitch];
    }

} // sendEndLabelDisplacement forIndexes


- (void) stitchEndLabelDisplacementChangedNotification: 
    (NSNotification *) notification
{
    NSDictionary *userinfo = [notification userInfo];

    NSIndexSet *indexes;
    indexes = [userinfo objectForKey: @"indexes"];

    NSNumber *number = [self notificationValue];
    float newEndLabelDisplacement = [number floatValue];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        [self setEndLabelDisplacement: newEndLabelDisplacement
              forIndexes: indexes];

    } [undoManager endUndoGrouping];


} // stitchEndLabelDisplacementChangedNotification


- (void) changeEndLabelDisplacement: (float) endLabelDisplacement
                           forIndexes: (NSIndexSet *) indexes
{
    NSNumber *number = [NSNumber numberWithFloat: endLabelDisplacement];

    NSNotification *notification;
    notification = [self notificationWithName:
                             @"stitchEndLabelDisplacementChanged"
                         indexes: indexes
                         value: number];

    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientEndLabelDisplacement: endLabelDisplacement];
    }

    [self setNeedsDisplay: YES];

} // changeEndLabelDisplacement:forIndexes:


// --------------------------------------------------

- (void) setStartLabelDisplacementAngle: (float) newStartLabelDisplacementAngle  
                              forStitch: (BWStitch *) stitch
{
    float oldStartLabelDisplacementAngle;
    oldStartLabelDisplacementAngle = [stitch startLabelDisplacementAngle];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setStartLabelDisplacementAngle: oldStartLabelDisplacementAngle  
        forStitch: stitch];

    [stitch setStartLabelDisplacementAngle: newStartLabelDisplacementAngle];

    [undoManager setActionName: @"Change Stitch StartLabelDisplacementAngle"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setStartLabelDisplacementAngle:forStitch:


- (void) setStartLabelDisplacementAngle: (float) newStartLabelDisplacementAngle
                             forIndexes: (NSIndexSet *) indexes
{
    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];
    
    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        
        [stitch setTransientStartLabelDisplacementAngle: 
                    BWStitch_NO_TRANSIENT_VALUE];
        
        [self setStartLabelDisplacementAngle: newStartLabelDisplacementAngle
              forStitch: stitch];
    }
    
} // setStartLabelDisplacementAngle forIndexes


- (void) stitchStartLabelDisplacementAngleChangedNotification: (NSNotification *) notification
{
    NSDictionary *userinfo = [notification userInfo];

    NSIndexSet *indexes;
    indexes = [userinfo objectForKey: @"indexes"];

    NSNumber *number = [self notificationValue];
    float newStartLabelDisplacementAngle = [number floatValue];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {

        [self setStartLabelDisplacementAngle: newStartLabelDisplacementAngle
              forIndexes: indexes];

    } [undoManager endUndoGrouping];

} // stitchStartLabelDisplacementAngleChangedNotification


- (void) changeStartLabelDisplacementAngle: (float) startLabelDisplacementAngle
                                forIndexes: (NSIndexSet *) indexes
{
    NSNumber *number = [NSNumber numberWithFloat: startLabelDisplacementAngle];

    NSNotification *notification;
    notification = [self notificationWithName:
                            @"stitchStartLabelDisplacementAngleChanged"
                         indexes: indexes
                         value: number];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientStartLabelDisplacementAngle: startLabelDisplacementAngle];
    }

    [self setNeedsDisplay: YES];

} // changeStartLabelDisplacementAngle:forIndexes


// --------------------------------------------------


- (void) setEndLabelDisplacementAngle: (float) newEndLabelDisplacementAngle  
                            forStitch: (BWStitch *) stitch
{
    float oldEndLabelDisplacementAngle;
    oldEndLabelDisplacementAngle = [stitch endLabelDisplacementAngle];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setEndLabelDisplacementAngle: oldEndLabelDisplacementAngle  
        forStitch: stitch];

    [stitch setEndLabelDisplacementAngle: newEndLabelDisplacementAngle];

    [undoManager setActionName: @"Change Stitch EndLabelDisplacementAngle"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setEndLabelDisplacementAngle:forStitch:


- (void) setEndLabelDisplacementAngle: (float) newEndLabelDisplacementAngle
                           forIndexes: (NSIndexSet *) indexes
{
    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        
        [stitch setTransientEndLabelDisplacementAngle: 
                    BWStitch_NO_TRANSIENT_VALUE];
        
        [self setEndLabelDisplacementAngle: newEndLabelDisplacementAngle
              forStitch: stitch];
    }

} // setEndLabelDisplacementAngle forIndexes


- (void) stitchEndLabelDisplacementAngleChangedNotification: (NSNotification *) notification
{
    NSDictionary *userinfo = [notification userInfo];

    NSIndexSet *indexes;
    indexes = [userinfo objectForKey: @"indexes"];

    NSNumber *number = [self notificationValue];
    float newEndLabelDisplacementAngle = [number floatValue];

    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        
        [self setEndLabelDisplacementAngle: newEndLabelDisplacementAngle
              forIndexes: indexes];

    } [undoManager endUndoGrouping];

} // stitchEndLabelDisplacementAngleChangedNotification


- (void) changeEndLabelDisplacementAngle: (float) endLabelDisplacementAngle
                                forIndexes: (NSIndexSet *) indexes
{
    NSNumber *number = [NSNumber numberWithFloat: endLabelDisplacementAngle];

    NSNotification *notification;
    notification = [self notificationWithName:
                            @"stitchEndLabelDisplacementAngleChanged"
                         indexes: indexes
                         value: number];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    NSArray *victims;
    victims = [stitches stitchesAtIndexes: indexes];

    int i;
    for (i = 0; i < [victims count]; i++) {
        BWStitch *stitch = [victims objectAtIndex: i];
        [stitch setTransientEndLabelDisplacementAngle: endLabelDisplacementAngle];
    }

    [self setNeedsDisplay: YES];

} // changeEndLabelDisplacementAngle:forIndexes


// --------------------------------------------------

- (void) setStartSquare: (BWGrid_Square) startSquare
             squarePart: (BWGrid_SquarePart) startPart
              endSquare: (BWGrid_Square) endSquare
             squarePart: (BWGrid_SquarePart) endPart
              forStitch: (BWStitch *) stitch;
{
    BWGrid_Square oldStartSquare, oldEndSquare;
    BWGrid_SquarePart oldStartPart, oldEndPart;

    [stitch getStartSquare: &oldStartSquare
            part: &oldStartPart];
    [stitch getEndSquare: &oldEndSquare
            part: &oldEndPart];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setStartSquare: oldStartSquare
        squarePart: oldStartPart
        endSquare: oldEndSquare
        squarePart: oldEndPart
        forStitch: stitch];

    [stitch setStartSquare: startSquare
            part: startPart];
    [stitch setEndSquare: endSquare
            part: endPart];

    [undoManager setActionName: @"Move Stitch"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setEndLabelDisplacementAngle:forStitch:


- (void) stitchLocationChangedNotification: (NSNotification *) notification
{
    BWStitch *stitch;
    stitch = [[notification userInfo] objectForKey: @"stitch"];

    BWGrid_Square startSquare, endSquare;
    BWGrid_SquarePart startPart, endPart;

    [stitch getTransientStartSquare: &startSquare
            part: &startPart];
    [stitch getTransientEndSquare: &endSquare
            part: &endPart];

    [stitch setTransientStartSquare: startSquare
            part: BWGrid_InvalidSquare];
    [stitch setTransientEndSquare: endSquare
            part: BWGrid_InvalidSquare];

    [self setStartSquare: startSquare
          squarePart: startPart
          endSquare: endSquare
          squarePart: endPart
          forStitch: stitch];

} // stitchLocationChanged


- (void) changeStartSquare: (BWGrid_Square) startSquare
                squarePart: (BWGrid_SquarePart) startPart
                 endSquare: (BWGrid_Square) endSquare
                squarePart: (BWGrid_SquarePart) endPart
                 forStitch: (BWStitch *) stitch;
{
    NSNotification *notification;
    notification = [self notificationWithName: @"stitchLocationChanged"
                         stitch: stitch];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    [stitch setTransientStartSquare: startSquare
            part: startPart];
    [stitch setTransientEndSquare: endSquare
            part: endPart];

    [self setNeedsDisplay: YES];

} // changeStartSquare


// --------------------------------------------------

- (void) setBendFactor: (float) bendFactor
          displacement: (float) displacement
              forStitch: (BWStitch *) stitch
{
    float oldBendFactor, oldBendDisplacement;

    oldBendFactor = [stitch bendFactor];
    oldBendDisplacement = [stitch bendDisplacement];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setBendFactor: oldBendFactor
        displacement: oldBendDisplacement
        forStitch: stitch];

    [stitch setBendFactor: bendFactor];
    [stitch setBendDisplacement: displacement];

    [undoManager setActionName: @"Stitch Bend"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setEndLabelDisplacementAngle:forStitch:


- (void) stitchBendChangedNotification: (NSNotification *) notification
{
    BWStitch *stitch;
    stitch = [[notification userInfo] objectForKey: @"stitch"];

    float bendFactor;
    float bendDisplacement;

    bendFactor = [stitch transientBendFactor];
    bendDisplacement = [stitch transientBendDisplacement];

    [stitch setTransientBendFactor: BWStitch_NO_TRANSIENT_VALUE];
    [stitch setTransientBendDisplacement: BWStitch_NO_TRANSIENT_VALUE];

    [self setBendFactor: bendFactor
          displacement: bendDisplacement
          forStitch: stitch];

} // stitchBendChangedNotification


- (void) changeBendFactor: (float) bendFactor
             displacement: (float) displacement
                forStitch: (BWStitch *) stitch;
{
    NSNotification *notification;
    notification = [self notificationWithName: @"stitchBendChanged"
                         stitch: stitch];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    [stitch setTransientBendFactor: bendFactor];
    [stitch setTransientBendDisplacement: displacement];

    [self setNeedsDisplay: YES];

} // changeEndLabelDisplacementAngle:forStitch:



- (void) setStartLabelVisibility: (BOOL) startVisible
              endLabelVisibility: (BOOL) endVisible
                customStartLabel: (NSString *) startLabel
                  customEndLabel: (NSString *) endLabel
                startLabelInside: (BOOL) startInside
                  endLabelInside: (BOOL) endInside
          startLabelDisplacement: (float) startDisplacement
            endLabelDisplacement: (float) endDisplacement
     startLabelDisplacementAngle: (float) startAngle
       endLabelDisplacementAngle: (float) endAngle
                      forIndexes: (NSIndexSet *) indexes
{
    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {
        [self setStartLabelVisibility: startVisible
              endLabelVisibility: endVisible
              forIndexes: indexes];

        [self removeCustomLabelsForIndexes: indexes];

        if (startLabel != nil) {
            [self setCustomStartLabel: startLabel
                  forIndexes: indexes];
        }
        if (endLabel != nil) {
            [self setCustomEndLabel: endLabel
                  forIndexes: indexes];
        }

        [self setStartLabelInside: startInside
              forIndexes: indexes];
        [self setEndLabelInside: endInside
              forIndexes: indexes];
        
        [self setStartLabelDisplacement: startDisplacement
              forIndexes: indexes];
        [self setStartLabelDisplacementAngle: startAngle
              forIndexes: indexes];
        [self setEndLabelDisplacement: endDisplacement
              forIndexes: indexes];
        [self setEndLabelDisplacementAngle: endAngle
              forIndexes: indexes];

        [undoManager setActionName: @"Change All Stitch Settings"];

    } [undoManager endUndoGrouping];



    [self setNeedsDisplay: YES];
    [self updateToolUI];

} // setStartLabelVisibility (set everything)



@end // StitchUndoMethods


@implementation BWBorkStitchDocument (FabricUndoMethods)


- (void) changeFabricFlatGrid: (BOOL) onOff
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        changeFabricFlatGrid: !onOff];

    [grid setFlatGrid: onOff];

    [undoManager setActionName: @"Change Grid Flatness"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // changeFabricFlatGrid



- (void) setupFabricUndo
{
    NSNotificationCenter *center;
    center = [NSNotificationCenter defaultCenter];

    [center addObserver: self
            selector: @selector(fabricWidthChangedNotification:)
            name: @"fabricWidthChanged"
            object: self];

    [center addObserver: self
            selector: @selector(fabricSpacingChangedNotification:)
            name: @"fabricSpacingChanged"
            object: self];

    [center addObserver: self
            selector: @selector(fabricBackgroundColorChangedNotification:)
            name: @"fabricBackgroundColorChanged"
            object: self];

    [center addObserver: self
            selector: @selector(fabricThreadColorChangedNotification:)
            name: @"fabricThreadColorChanged"
            object: self];

    [center addObserver: self
            selector: @selector(fabricEdgeColorChangedNotification:)
            name: @"fabricEdgeColorChanged"
            object: self];

} // setupFabricUndo


// -----------------------------------------------------------

- (void) setFabricWidth: (float) newWidth
{
    float oldWidth;
    oldWidth = [grid fabricWidth];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setFabricWidth: oldWidth];

    [grid setFabricWidth: newWidth];

    [undoManager setActionName: @"Change Fabric Width"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setFabricWidth


- (void) fabricWidthChangedNotification: (NSNotification *) notification
{
    float newWidth = [grid transientFabricWidth];
    [grid setTransientFabricWidth: BWGrid_NO_TRANSIENT_VALUE];

    [self setFabricWidth: newWidth];

} // fabricWidthChangedNotification


- (void) changeFabricWidth: (float) newWidth
{
    NSNotification *notification;
    notification = 
        [NSNotification notificationWithName: @"fabricWidthChanged"
                        object: self];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    [grid setTransientFabricWidth: newWidth];
    [self setNeedsDisplay: YES];

} // changeFabricWidth



// -----------------------------------------------------------


- (void) setFabricSpacing: (float) newSpacing
{
    float oldSpacing;
    oldSpacing = [grid fabricSpacing];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setFabricSpacing: oldSpacing];

    [grid setFabricSpacing: newSpacing];

    [undoManager setActionName: @"Change Fabric Spacing"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setFabricSpacing


- (void) fabricSpacingChangedNotification: (NSNotification *) notification
{
    float newSpacing = [grid transientFabricSpacing];
    [grid setTransientFabricSpacing: BWGrid_NO_TRANSIENT_VALUE];

    [self setFabricSpacing: newSpacing];

} // fabricSpacingChangedNotification


- (void) changeFabricSpacing: (float) newSpacing
{
    NSNotification *notification;
    notification = 
        [NSNotification notificationWithName: @"fabricSpacingChanged"
                        object: self];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    [grid setTransientFabricSpacing: newSpacing];
    [self setNeedsDisplay: YES];

} // changeFabricSpacing



// --------------------------------------------------

- (void) setFabricBackgroundColor: (NSColor *) newBackgroundColor
{
    NSColor *oldBackgroundColor;
    oldBackgroundColor = [grid backgroundColor];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setFabricBackgroundColor: oldBackgroundColor];

    [grid setBackgroundColor: newBackgroundColor];

    [undoManager setActionName: @"Change Fabric Background Color"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setFabricBackgroundColor


- (void) fabricBackgroundColorChangedNotification: (NSNotification *) notification
{
    NSColor *newBackgroundColor = [grid transientBackgroundColor];
    [grid setTransientBackgroundColor: NULL];

    [self setFabricBackgroundColor: newBackgroundColor];

} // fabricBackgroundColorChangedNotification


- (void) changeFabricBackgroundColor: (NSColor *) newBackgroundColor
{
    NSNotification *notification;
    notification = 
        [NSNotification notificationWithName: @"fabricBackgroundColorChanged"
                        object: self];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    [grid setTransientBackgroundColor: newBackgroundColor];
    [self setNeedsDisplay: YES];

} // changeFabricBackgroundColor


// --------------------------------------------------

- (void) setFabricThreadColor: (NSColor *) newThreadColor
{
    NSColor *oldThreadColor;
    oldThreadColor = [grid threadColor];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setFabricThreadColor: oldThreadColor];

    [grid setThreadColor: newThreadColor];

    [undoManager setActionName: @"Change Fabric Thread Color"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setFabricThreadColor


- (void) fabricThreadColorChangedNotification: (NSNotification *) notification
{
    NSColor *newThreadColor = [grid transientThreadColor];
    [grid setTransientThreadColor: NULL];

    [self setFabricThreadColor: newThreadColor];

} // fabricThreadColorChangedNotification


- (void) changeFabricThreadColor: (NSColor *) newThreadColor
{
    NSNotification *notification;
    notification = 
        [NSNotification notificationWithName: @"fabricThreadColorChanged"
                        object: self];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    [grid setTransientThreadColor: newThreadColor];
    [self setNeedsDisplay: YES];

} // changeFabricThreadColor


// --------------------------------------------------

- (void) setFabricEdgeColor: (NSColor *) newEdgeColor
{
    NSColor *oldEdgeColor;
    oldEdgeColor = [grid edgeColor];

    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setFabricEdgeColor: oldEdgeColor];

    [grid setEdgeColor: newEdgeColor];

    [undoManager setActionName: @"Change Thread Edge Color"];

    [self setNeedsDisplay: YES];

    [self updateToolUI];

} // setFabricEdgeColor


- (void) fabricEdgeColorChangedNotification: (NSNotification *) notification
{
    NSColor *newEdgeColor = [grid transientEdgeColor];
    [grid setTransientEdgeColor: NULL];

    [self setFabricEdgeColor: newEdgeColor];

} // fabricEdgeColorChangedNotification


- (void) changeFabricEdgeColor: (NSColor *) newEdgeColor
{
    NSNotification *notification;
    notification = 
        [NSNotification notificationWithName: @"fabricEdgeColorChanged"
                        object: self];
    
    [undoQueue enqueueNotification: notification
               postingStyle: NSPostWhenIdle
               coalesceMask: NAME_SENDER_MASK
               forModes: nil];

    [grid setTransientEdgeColor: newEdgeColor];
    [self setNeedsDisplay: YES];

} // changeFabricEdgeColor


- (void) setFabricWidth: (float) newWidth
                spacing: (float) spacing
                   flat: (BOOL) flat
             background: (NSColor *) backgroundColor
                 thread: (NSColor *) threadColor
                   edge: (NSColor *) edgeColor
{
    NSUndoManager *undoManager = [self undoManager];

    [undoManager beginUndoGrouping]; {

        [self setFabricWidth: newWidth];
        [self setFabricSpacing: spacing];
        
        [self changeFabricFlatGrid: flat];

        [self setFabricBackgroundColor: backgroundColor];
        [self setFabricThreadColor: threadColor];
        [self setFabricEdgeColor: edgeColor];

    } [undoManager endUndoGrouping];

    [undoManager setActionName: @"Change All Fabric Settings"];

    [self setNeedsDisplay: YES];
    [self updateToolUI];

} // setFabricWidth


@end // FabricUndoMethods




